{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- https://time.geekbang.org/column/article/718865"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "无论对人，还是对事儿，第一印象都很重要，Rust 也不例外。\n",
    "\n",
    "今天我们就来看一看 Rust 给人的第一印象是什么吧。其实 Rust 宣称的安全、高性能、无畏并发这些特点，初次接触的时候都是感受不到的。\n",
    "\n",
    "第一次能直观感受到的实际是下面这些东西。\n",
    "- Rust 代码长什么样儿？\n",
    "- Rust 在编辑器里面体验如何？\n",
    "- Rust 工程如何创建？\n",
    "- Rust 程序如何编译、执行？\n",
    "\n",
    "下面我们马上下载安装 Rust，快速体验一波。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 下载安装\n",
    "\n",
    "要做 Rust 编程开发，安装 Rust 编译器套件是第一步。如果是在 MacOS 或 Linux 下，只需要执行："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "! curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "按提示执行操作，就安装好了，非常方便。而如果你使用的是 Windows 系统，那么会有更多选择。\n",
    "\n",
    "你既可以在 WSL 中开发编译 Rust 代码，也可以在 Windows 原生平台上开发 Rust 代码。\n",
    "\n",
    "如果你计划在 WSL 中开发，安装方式与上面一致。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "! curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果想在 Windows 原生平台上开发 Rust 代码，首先需要确定安装 32 位的版本还是 64 位的版本。\n",
    "\n",
    "在安装过程中，它会询问你是想安装 GNU 工具链的版本还是 MSVC 工具链的版本。\n",
    "> https://www.rust-lang.org/tools/install\n",
    "\n",
    "安装 GNU 工具链版本的话，不需要额外安装其他软件包。而安装 MSVC 工具链的话，需要先安装微软的 [Visual Studio](https://kaisery.github.io/trpl-zh-cn/ch01-01-installation.html) 依赖。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果你暂时不想在本地安装，或者本地安装有问题，对于我们初学者来说，也有一个方便、快捷的方式，就是 Rust 语言官方提供的一个网页端的[Rust 试验场](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021)，可以让你快速体验。\n",
    "\n",
    "这个网页 Playground 非常方便，可以用来快速验证一些代码片段，也便于将代码分享给别人。如果你的电脑本地没有安装 Rust 套件，可以临时使用这个 Playground 学习。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> https://godbolt.org/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 编辑器 / IDE\n",
    "\n",
    "Rust 编译器套件安装好之后，会提供一些工具，这里我们选几个主要的简单介绍一下。\n",
    "\n",
    "开发 Rust，除了下载、安装 Rust 本身之外，还有一个工具也推荐你使用，就是 VS Code。需要提醒你的是，在 VS Code 中需要安装 rust-analyzer 插件才会有自动提示等功能。\n",
    "\n",
    "你可以看一下 VS Code 编辑 Rust 代码的效果。\n",
    "\n",
    "VS Code 功能非常强大，除了基本的 IDE 功能外，还能实现远程编辑。比如在 Windows 下开发，代码放在 WSL Linux 里面，在 Windows Host 下使用 VS Code 远程编辑 WSL 中的代码，体验非常棒。其他一些常用的 Rust 代码编辑器还有 VIM、NeoVIM、IDEA、Clion 等。\n",
    "\n",
    "JetBrains 最近推出了 Rust 专用的 IDE：RustRover，如果有精力的话，你也可以下载下来体验一下。\n",
    "\n",
    "\n",
    "|    命令     |                             用途                             |                             举例                             |\n",
    "| :---------: | :----------------------------------------------------------: | :----------------------------------------------------------: |\n",
    "|    cargo    | Rust的包管理器，构建工具和依赖解决器。可以使用`cargo`命令创建、编辑和构建Rust项目 | `cargo new--bin my_project`可以创建一个名为`my_project`的新的Rust项目 |\n",
    "|   rustup    | 用来升级维护Rust编译器套件的版本，同时支持维护多个版本，并可用来安装Rust组件 |     `rustup update stable`可将Rust stable版本升级至最新      |\n",
    "|  rust-fmt   | 可用来对Rust代码按配置格式进行自动排版，用来统一Rust代码风格 | 配合cargo,直接在工程目录下运行`cargo fmt`就可以对整个工程进行排版 |\n",
    "| rust-clippy |   可用来对Rust代码进行严谨性检查，指出一些写得不规范的地方   | 配合cargo,直接在工程目录下运行`cargo clippy`就可以对整个工程进行检查了 |\n",
    "\n",
    "工具齐备了，下面我们马上体验起来，先来创建一个 Rust 工程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 创建一个工程\n",
    "\n",
    "创建工程我们应该使用哪个工具呢？ 没错，就是刚刚我们提到的 `cargo` 命令行工具。我们用它来创建一个 Rust 工程 `helloworld`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    Creating binary (application) `helloworld` package\n",
      "note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n"
     ]
    }
   ],
   "source": [
    "! cargo new --bin helloworld "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".\n",
      "├── Cargo.toml\n",
      "└── src\n",
      "    └── main.rs\n",
      "\n",
      "1 directory, 2 files\n"
     ]
    }
   ],
   "source": [
    "! cd helloworld && tree"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "第一层是一个 `src` 目录和一个 `Cargo.toml` 配置文件。\n",
    "\n",
    "`src` 是放置源代码的地方，而 `Cargo.toml` 是这个工程的配置文件，我们来看一下里面的内容"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "[package]\n",
    "name = \"helloworld\"\n",
    "version = \"0.1.0\"\n",
    "edition = \"2021\"\n",
    "\n",
    "# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n",
    "\n",
    "[dependencies]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Cargo.toml` 中包含 package 等基本信息，里面有名字、版本和采用的 Rust 版次。\n",
    "\n",
    "Rust 3 年发行一个版次，目前有 2015、2018 和 2021 版次，最新的是 2021 版次，也是我们这门课使用的版次。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以执行 `rustc -V` 来查看我们课程使用的 Rust 版本。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "rustc 1.81.0 (eeb90cda1 2024-09-04)\n"
     ]
    }
   ],
   "source": [
    "! rustc -V"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "好了，一切就绪后，我们可以来看看 `src` 下的 `main.rs` 里面的代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Hello, World"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello, world!\n"
     ]
    }
   ],
   "source": [
    "fn main() {\n",
    "    println!(\"Hello, world!\");\n",
    "}\n",
    "\n",
    "main();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello, world!\n"
     ]
    }
   ],
   "source": [
    "println!(\"Hello, world!\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这段代码的意思是，我们要在终端输出这个 \"Hello, world!\" 的字符串。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 `cargo build` 来编译。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   Compiling helloworld v0.1.0 (/mnt/d/dev_learn/learn_program_language_notes/rust/100626901_Rust_by_tanggang/01/helloworld)\n",
      "    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.05s\n"
     ]
    }
   ],
   "source": [
    "! cd helloworld && cargo build"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 `cargo run`命令可以直接运行程序。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s\n",
      "     Running `target/debug/helloworld`\n",
      "Hello, world!\n"
     ]
    }
   ],
   "source": [
    "! cd helloworld && cargo run"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "source": [
    "可以看到，最后终端打印出了 `Hello, world`。我们成功地执行了第一个 Rust 程序。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Rust 基础语法\n",
    "\n",
    "快速体验 Hello World 后，你是不是对 Rust 已经有了一个感性的认识？不过只是会 Hello World 的话，我们离入门 Rust 尚远。\n",
    "\n",
    "下面我们就从 Rust 的基础语法入手开始了解这门语言，为今后使用 Rust 编程打下一个良好的基础。\n",
    "\n",
    "Rust 基础语法主要包括基础类型、复合类型、控制流、函数与模块几个方面，下面我带你一个一个看。\n",
    "\n",
    "<img src=\"./images/01.webp\" alt=\"img\" style=\"zoom:40%;\" />\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rust 中的基础类型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 赋值语句\n",
    "\n",
    "Rust 中使用 `let` 关键字定义变量及初始化，你可以看一下我给出的这个例子。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "fn main() {\n",
    "  let a: u32 = 1;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，Rust 中类型写在变量名的后面，例子里变量 `a` 的类型是 `u32`, 也就是无符号 32 位整数，赋值为 `1`。\n",
    "\n",
    "Rust 保证你定义的变量在第一次使用之前一定被初始化过。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数字类型\n",
    "\n",
    "与一些动态语言不同，Rust 中的数字类型是区分位数的。我们先来看整数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 整数\n",
    "\n",
    "|  长度   | 有符号 | 无符号 |\n",
    "| :-----: | :----: | :----: |\n",
    "|  8-bit  |   `i8`   |   `u8`   |\n",
    "| 16-bit  |  `i16`   |  `u16`   |\n",
    "| 32-bit  |  `i32`   |  `u32`   |\n",
    "| 64-bit  |  `i64`   |  `u64`   |\n",
    "| 128-bit |  `i128`  |  `u128`  |\n",
    "|  arch   | `isize`  | `usize`  |\n",
    "\n",
    "其中，`isize` 和 `usize` 的位数与具体 CPU 架构位数有关。CPU 是 64 位的，它们就是 64 位的，CPU 是 32 位的，它们就是 32 位的。\n",
    "\n",
    "这些整数类型可以在写字面量的时候作为后缀跟在后面，来直接指定值的类型，比如 `let a = 10u32;` 就定义了一个变量 `a`，初始化成无符号 32 位整型，值为 `10`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let a = 10u32;\n",
    "\n",
    "a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 整数字面量的辅助写法\n",
    "\n",
    "Rust 提供了灵活的数字表示方法，便于我们编写整数字面量。\n",
    "\n",
    "比如：\n",
    "- 10进制字面量 `98_222`，使用下划线按三位数字一组隔开\n",
    "- 16进制字面量 `0xff`，使用`0x`开头\n",
    "- 8进制字面量 `0o77`，使用`0o`（小写字母`o`）开头\n",
    "- 2进制字面量 `0b1111_0000`，使用`0b`开头，按4位数字一组隔开\n",
    "- 字符的字节表示 `b'A'`，对一个ASCII字符，在其前面加前`b`缀，直接得到此字符的ASCII码值\n",
    "\n",
    "各种形式的辅助写法是为了提高程序员写整数字面量的效率，同时更清晰，更不容易犯错。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 浮点数\n",
    "\n",
    "浮点数有两种类型：`f32` 和 `f64`，分别代表 32 位浮点数类型和 64 位浮点数类型。\n",
    "\n",
    "它们也可以跟在字面量的后面，用来指定浮点数值的类型，比如 `let a = 10.0f32;` 就定义了一个变量 `a`，初始化成 32 位浮点数类型，值为 `10.0`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10.0"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let a = 10.0f32;\n",
    "\n",
    "a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 布尔类型\n",
    "\n",
    "Rust 中的布尔类型为 `bool`，它只有两个值，`true` 和 `false`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "true"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let a = true;\n",
    "\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "false"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let b: bool = false;\n",
    "\n",
    "b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 字符\n",
    "\n",
    "Rust 中的字符类型是 `char`，值用单引号括起来。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "c: z\n",
      "z: ℤ\n",
      "heart_eyed_cat: 😻\n",
      "t: 中\n"
     ]
    }
   ],
   "source": [
    "let c = 'z';\n",
    "println!(\"c: {}\", c);\n",
    "\n",
    "let z: char = 'ℤ'; \n",
    "println!(\"z: {}\", z);\n",
    "\n",
    "let heart_eyed_cat = '😻';\n",
    "println!(\"heart_eyed_cat: {}\", heart_eyed_cat);\n",
    "\n",
    "let t = '中';\n",
    "println!(\"t: {}\", t);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Rust 的 char 类型存的是 [~~Unicode 散列值~~ unicode_scalar_value](https://unicode.org/glossary/#unicode_scalar_value)。这意味着它可以表达各种符号，比如中文符号、emoji 符号等。\n",
    "\n",
    "在 Rust 中，`char` 类型在内存中总是[占用 4 个字节大小](https://doc.rust-lang.org/std/primitive.char.html#representation)。这一点与 C 语言或其他某些语言中的 `char` 有很大不同。\n",
    "\n",
    "> 在C语言中,` char`通常是1个字节,表示单个ASCII字符或多字节字符编码的一个字节。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "UTF-8编码占用的字节数: 6\n",
      "char数组占用的字节数: 8\n",
      "每个char占用的字节数: 4\n",
      "char数组中的字符数: 2\n"
     ]
    }
   ],
   "source": [
    "use std::mem;\n",
    "\n",
    "fn main() {\n",
    "    let s = \"你好\";\n",
    "\n",
    "    // 计算UTF-8编码占用的字节数\n",
    "    let utf8_size = s.len();\n",
    "    println!(\"UTF-8编码占用的字节数: {}\", utf8_size);\n",
    "\n",
    "    // 将字符串转换为char数组并计算占用的字节数\n",
    "    let char_vec: Vec<char> = s.chars().collect();\n",
    "    let char_array_size = mem::size_of_val(&char_vec[..]);\n",
    "    println!(\"char数组占用的字节数: {}\", char_array_size);\n",
    "\n",
    "    // 验证每个char占用4个字节\n",
    "    println!(\"每个char占用的字节数: {}\", mem::size_of::<char>());\n",
    "\n",
    "    // 计算char数组中的字符数\n",
    "    let char_count = char_vec.len();\n",
    "    println!(\"char数组中的字符数: {}\", char_count);\n",
    "}\n",
    "\n",
    "main();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 字符串\n",
    "\n",
    "Rust 中的字符串类型是 `String`。虽然中文表述上，字符串只比前面的字符类型多了一个串字，但它们内部存储结构完全不同。`String` 内部存储的是 Unicode 字符串的 UTF8 编码，而 `char` 直接存的是 Unicode Scalar Value（二者的区别可查阅[这里](https://stackoverflow.com/questions/48465265/what-is-the-difference-between-unicode-code-points-and-unicode-scalars)）。也就是说 `String` 不是 `char` 的数组，这点与 C 语言也有很大区别。\n",
    "\n",
    "通过下面示例我们可以看到，Rust 字符串对 Unicode 字符集有着良好的支持。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello: السلام عليكم\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello: Dobrý den\n",
      "hello: Hello\n",
      "hello: שָׁלוֹם\n",
      "hello: नमस्ते\n",
      "hello: こんにちは\n",
      "hello: 안녕하세요\n",
      "hello: 你好\n",
      "hello: Olá\n",
      "hello: Здравствуйте\n",
      "hello: Hola\n"
     ]
    }
   ],
   "source": [
    "let hello = String::from(\"السلام عليكم\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"Dobrý den\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"Hello\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"שָׁלוֹם\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"नमस्ते\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"こんにちは\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"안녕하세요\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"你好\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"Olá\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"Здравствуйте\");\n",
    "println!(\"hello: {}\", hello);\n",
    "\n",
    "let hello = String::from(\"Hola\");\n",
    "println!(\"hello: {}\", hello);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，Rust 中的 `String` 不能通过下标去访问。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "ename": "Error",
     "evalue": "consider importing this trait",
     "output_type": "error",
     "traceback": [
      "help: consider importing this trait: `use std::ops::Index;\n`",
      "consider importing this trait"
     ]
    },
    {
     "ename": "Error",
     "evalue": "the type `str` cannot be indexed by `{integer}`",
     "output_type": "error",
     "traceback": [
      "let a = hello[0];  // 你可能想把“你”字取出来，但实际上这样是错误的",
      "              ^ string indices are ranges of `usize`",
      "the type `str` cannot be indexed by `{integer}`",
      "help: the trait `SliceIndex<str>` is not implemented for `{integer}`, which is required by `String: Index<_>`",
      "help: the trait `SliceIndex<[_]>` is implemented for `usize`",
      "help: for that trait implementation, expected `[_]`, found `str`"
     ]
    }
   ],
   "source": [
    "let hello = String::from(\"你好\");\n",
    "let a = hello[0];  // 你可能想把“你”字取出来，但实际上这样是错误的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为什么呢？你可以想一想。因为 `String` 存储的 Unicode 序列的 UTF8 编码，而 UTF8 编码是变长编码。这样即使能访问成功，也只能取出一个字符的 UTF8 编码的第一个字节，它很可能是没有意义的。因此 Rust 直接对 `String` 禁止了这个索引操作。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 字符串字面量中的转义\n",
    "\n",
    "与 C 语言一样，Rust 中转义符号也是反斜杠 `\\` ，可用来转义各种字符。\n",
    "\n",
    "你可以运行我给出的这几个示例来理解一下。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I'm saying \"Hello\"\n",
      "I'm saying \n",
      " 你好\n",
      "I'm saying \n",
      " 你好\n",
      "I'm saying \\ Ok\n",
      "I'm saying hello.\u0000\n"
     ]
    }
   ],
   "source": [
    "// 将\"\"号进行转义\n",
    "let byte_escape = \"I'm saying \\\"Hello\\\"\";\n",
    "println!(\"{}\", byte_escape);\n",
    "\n",
    "// 分两行打印\n",
    "let byte_escape = \"I'm saying \\n 你好\";\n",
    "println!(\"{}\", byte_escape);\n",
    "\n",
    "// Windows下的换行符\n",
    "let byte_escape = \"I'm saying \\r\\n 你好\";\n",
    "println!(\"{}\", byte_escape);\n",
    "\n",
    "// 打印出 \\ 本身\n",
    "let byte_escape = \"I'm saying \\\\ Ok\";\n",
    "println!(\"{}\", byte_escape);\n",
    "\n",
    "// 强行在字符串后面加个0，与C语言的字符串一致。\n",
    "let byte_escape = \"I'm saying hello.\\0\";\n",
    "println!(\"{}\", byte_escape);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "除此之外，Rust 还支持通过 `\\x` 输入等值的 ASCII 字符，以及通过 `\\u{}` 输入等值的 Unicode 字符。\n",
    "\n",
    "你可以看一下我给出的这两个例子。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I'm saying hello \n",
      "I'm saying hello e\n"
     ]
    }
   ],
   "source": [
    "// 使用 \\x 输入等值的ASCII字符（最高7位）\n",
    "let byte_escape = \"I'm saying hello \\x7f\";\n",
    "println!(\"{}\", byte_escape);\n",
    "\n",
    "// 使用 \\u{} 输入等值的Unicode字符（最高24位）\n",
    "let byte_escape = \"I'm saying hello \\u{0065}\";\n",
    "println!(\"{}\", byte_escape);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注：字符串转义的详细知识点，请参考 [Tokens - The Rust Reference (rust-lang.org)](https://doc.rust-lang.org/reference/tokens.html#character-escapes)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 禁止转义的字符串字面量\n",
    "\n",
    "有时候，我们不希望字符串被转义，也就是想输出原始字面量。\n",
    "\n",
    "这个在 Rust 中也有办法，使用 `r\"\"` 或 `r#\"\"#` 把字符串字面量套起来就行了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Escapes don't work here: \\x3F \\u{211D}\n"
     ]
    }
   ],
   "source": [
    "// 字符串字面量前面加r，表示不转义\n",
    "let raw_str = r\"Escapes don't work here: \\x3F \\u{211D}\";\n",
    "println!(\"{}\", raw_str);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "And then I said: \"There is no escape!\"\n"
     ]
    }
   ],
   "source": [
    "// 这个字面量必须使用r##这种形式，因为我们希望在字符串字面量里面保留\"\"\n",
    "let quotes = r#\"And then I said: \"There is no escape!\"\"#;\n",
    "println!(\"{}\", quotes);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A string with \"# in it. And even \"##!\n"
     ]
    }
   ],
   "source": [
    "// 如果遇到字面量里面有#号的情况，可以在r后面，加任意多的前后配对的#号，\n",
    "// 只要能帮助Rust编译器识别就行\n",
    "let longer_delimiter = r###\"A string with \"# in it. And even \"##!\"###;\n",
    "println!(\"{}\", longer_delimiter);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一点小提示，Rust 中的字符串字面量都支持换行写，默认把换行符包含进去。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 字节串\n",
    "\n",
    "很多时候，我们的字符串字面量中用不到 Unicode 字符，只需要 ASCII 字符集。\n",
    "\n",
    "对于这种情况，Rust 还有一种更紧凑的表示法：字节串。用 `b` 开头，双引号括起来，比如 `b\"this is a byte string\"`。这时候字符串的类型已不是字符串，而是字节的数组 `[u8; N]`，`N` 为字节数。\n",
    "\n",
    "你可以在 Playground 里面运行一下代码，看看输出什么。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A byte string: [116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 98, 121, 116, 101, 32, 115, 116, 114, 105, 110, 103]\n"
     ]
    }
   ],
   "source": [
    "// 字节串的类型是字节的数组，而不是字符串了\n",
    "let bytestring: &[u8; 21] = b\"this is a byte string\";\n",
    "println!(\"A byte string: {:?}\", bytestring);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Some escaped bytes: [82, 117, 115, 116, 32, 97, 115, 32, 98, 121, 116, 101, 115]\n"
     ]
    }
   ],
   "source": [
    "// 可以看看下面这串打印出什么\n",
    "let escaped = b\"\\x52\\x75\\x73\\x74 as bytes\";\n",
    "println!(\"Some escaped bytes: {:?}\", escaped);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[92, 117, 123, 50, 49, 49, 68, 125, 32, 105, 115, 32, 110, 111, 116, 32, 101, 115, 99, 97, 112, 101, 100, 32, 104, 101, 114, 101]\n"
     ]
    }
   ],
   "source": [
    "// 字节串与原始字面量结合使用\n",
    "let raw_bytestring = br\"\\u{211D} is not escaped here\";\n",
    "println!(\"{:?}\", raw_bytestring);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "字节串很有用，特别是在做系统级编程或网络协议开发的时候，经常会用到。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数组\n",
    "\n",
    "Rust 中的数组是 `array` 类型，用于存储同一类型的多个值。\n",
    "\n",
    "数组表示成`[T; N]`，由中括号括起来，中间用分号隔开，分号前面表示类型，分号后面表示数组长度。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a [1, 2, 3, 4, 5]\n"
     ]
    }
   ],
   "source": [
    "let a: [i32; 5] = [1, 2, 3, 4, 5];\n",
    "println!(\"a {:?}\", a);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a [1, 2, 3, 4, 5]\n"
     ]
    }
   ],
   "source": [
    "let a = [1, 2, 3, 4, 5];\n",
    "println!(\"a {:?}\", a);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Rust 中的数组是固定长度的，也就是说在编译阶段就能知道它占用的字节数，并且在运行阶段，不能改变它的长度（尺寸）。\n",
    "\n",
    "听到这里你是不是想说，这岂不是很麻烦？Rust 中的数组竟然不能改变长度。这里我解释一下，Rust 中区分固定尺寸数组和动态数组。之所以做这种区分是因为 Rust 语言在设计时就要求适应不同的场合，要有足够的韧性能在不同的场景中都达到最好的性能。因为固定尺寸的数据类型是可以直接放栈上的，创建和回收都比在堆上动态分配的动态数组性能要好。\n",
    "\n",
    "**是否能在编译期计算出某个数据类型在运行过程中占用内存空间的大小**，这个指标很重要，Rust 的类型系统就是按这个对类型进行分类的。后面的课程中我们会经常用到这个指标。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数组常用于开辟一个固定大小的 Buffer（缓冲区），用来接收 IO 输入输出等。\n",
    "\n",
    "也常用已知元素个数的字面量集合来初始化，比如表达一年有 12 个月。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"]"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let months = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\",\n",
    "              \"August\", \"September\", \"October\", \"November\", \"December\"];\n",
    "\n",
    "months"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数组的访问，可以用下标索引。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "fn main() {\n",
    "    let a: [i32; 5] = [1, 2, 3, 4, 5];\n",
    "    let b = a[0];\n",
    "    println!(\"{}\", b)\n",
    "}\n",
    "\n",
    "main();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们再来看看，如果下标索引越界了会发生什么。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "ename": "Error",
     "evalue": "this operation will panic at runtime",
     "output_type": "error",
     "traceback": [
      "\u001b[31m[unconditional_panic] Error:\u001b[0m this operation will panic at runtime",
      "   \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0mcommand_24:1:1\u001b[38;5;246m]\u001b[0m",
      "   \u001b[38;5;246m│\u001b[0m",
      " \u001b[38;5;246m3 │\u001b[0m \u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mb\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;54ma\u001b[0m\u001b[38;5;54m[\u001b[0m\u001b[38;5;54m5\u001b[0m\u001b[38;5;54m]\u001b[0m\u001b[38;5;249m;\u001b[0m",
      " \u001b[38;5;240m  │\u001b[0m             \u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m┬\u001b[0m\u001b[38;5;54m─\u001b[0m  ",
      " \u001b[38;5;240m  │\u001b[0m               \u001b[38;5;54m╰\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m index out of bounds: the length is 5 but the index is 5",
      "\u001b[38;5;246m───╯\u001b[0m"
     ]
    }
   ],
   "source": [
    "fn main() {\n",
    "    let a: [i32; 5] = [1, 2, 3, 4, 5]; // 编译器就可发现\n",
    "    let b = a[5];\n",
    "    println!(\"{}\", b)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这时候你可能已经发现了，Rust 在编译的时候，就给我们指出了问题，说这个操作会在运行的时候崩溃。\n",
    "\n",
    "为什么 Rust 能指出来呢？就是因为数组的长度是确定的，Rust 在编译时就分析并提取了这个数组类型占用空间长度的信息，因此直接阻止了你的越界访问。\n",
    "\n",
    "不得不说，Rust 太贴心了。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 动态数组\n",
    "\n",
    "Rust 中的动态数组类型是 `Vec`（Vector），也就是向量，中文翻译成动态数组。它用来存储同一类型的多个值，容量可在程序运行的过程中动态地扩大或缩小，因此叫做动态数组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "v [1, 2, 3]\n"
     ]
    }
   ],
   "source": [
    "let v: Vec<i32> = Vec::new();\n",
    "let v = vec![1, 2, 3];\n",
    "\n",
    "println!(\"v {:?}\", v);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "v [5, 6, 7, 8]\n"
     ]
    }
   ],
   "source": [
    "let mut v = Vec::new();\n",
    "v.push(5);\n",
    "v.push(6);\n",
    "v.push(7);\n",
    "v.push(8);\n",
    "\n",
    "println!(\"v {:?}\", v);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "动态数组可以用下标进行索引访问。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "v[0] \"superman 1\"\n",
      "v[3] \"superman 4\"\n"
     ]
    }
   ],
   "source": [
    "let s1 = String::from(\"superman 1\");\n",
    "let s2 = String::from(\"superman 2\");\n",
    "let s3 = String::from(\"superman 3\");\n",
    "let s4 = String::from(\"superman 4\");\n",
    "\n",
    "let v = vec![s1, s2, s3, s4];\n",
    "\n",
    "println!(\"v[0] {:?}\", v[0]);\n",
    "println!(\"v[3] {:?}\", v[3]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果我们下标越界了会发生什么？Rust 能继续帮我们提前找出问题来吗？试一试就知道了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "thread '<unnamed>' panicked at src/lib.rs:124:24:\n",
      "index out of bounds: the len is 4 but the index is 4\n",
      "stack backtrace:\n",
      "   0: rust_begin_unwind\n",
      "             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/panicking.rs:665:5\n",
      "   1: core::panicking::panic_fmt\n",
      "             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/panicking.rs:74:14\n",
      "   2: core::panicking::panic_bounds_check\n",
      "             at /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/core/src/panicking.rs:276:5\n",
      "   3: std::panic::catch_unwind\n",
      "   4: run_user_code_23\n",
      "   5: evcxr::runtime::Runtime::run_loop\n",
      "   6: evcxr::runtime::runtime_hook\n",
      "   7: evcxr_jupyter::main\n",
      "note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.\n"
     ]
    }
   ],
   "source": [
    "println!(\"v[4] {:?}\", v[4]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，这段代码正确通过了编译，但在运行的时候出错了，并且导致了主线程的崩溃。\n",
    "\n",
    "你可以将其与前面讲的数组 `array` 下标越界时的预警行为对比理解。为什么 `array` 的越界访问能在编译阶段检查出来，而 `Vec` 的越界访问不能在编译阶段检查出来呢？你可以好好想一想。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 哈希表\n",
    "\n",
    "哈希表是一种常见的结构，用于存储 Key-Value 映射关系，基本在各种语言中都有内置提供。\n",
    "\n",
    "Rust 中的哈希表类型为 `HashMap`。对一个 `HashMap` 结构来说，Key 要求是同一种类型，比如是字符串就统一用字符串，是数字就统一用数字。Value 也是一样，要求是同一种类型。Key 和 Value 的类型不需要相同。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"Blue\": 10, \"Yellow\": 50}"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "use std::collections::HashMap;\n",
    "\n",
    "let mut scores = HashMap::new();\n",
    "scores.insert(String::from(\"Blue\"), 10);\n",
    "scores.insert(String::from(\"Yellow\"), 50);\n",
    "\n",
    "scores"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因为哈希表能从一个键索引到一个值，所以应用场景非常广泛，后面我们还会仔细分析它的用法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rust 中的复合类型\n",
    "\n",
    "复合类型可以包含多种基础类型，是一种将类型进行有效组织的方式，提供了一级一级搭建更高层类型的能力。\n",
    "\n",
    "Rust 中的复合类型包括元组、结构体、枚举等。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 元组\n",
    "\n",
    "元组是一个固定（元素）长度的列表，每个元素类型可以不一样。用小括号括起来，元素之间用逗号隔开。\n",
    "\n",
    "例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(500, 6.4, 1)"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let tup: (i32, f64, u8) = (500, 6.4, 1);\n",
    "\n",
    "tup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "元组元素的访问："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "five_hundred: 500\n",
      "six_point_four: 6.4\n",
      "one: 1\n"
     ]
    }
   ],
   "source": [
    "let x: (i32, f64, u8) = (500, 6.4, 1);\n",
    "\n",
    "// 元组使用.运算符访问其元素，下标从0开始，注意语法\n",
    "let five_hundred = x.0;\n",
    "println!(\"five_hundred: {}\", five_hundred);\n",
    "\n",
    "let six_point_four = x.1;\n",
    "println!(\"six_point_four: {}\", six_point_four);\n",
    "\n",
    "let one = x.2;\n",
    "println!(\"one: {}\", one);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与数组的相同点是，它们都是固定元素个数的，在运行时不可伸缩。与数组的不同点是，元组的每个元素的类型可以不一样。\n",
    "\n",
    "元组在 Rust 中很有用，因为它可以用于函数的返回值，相当于把多个想返回的值捆绑在一起，一次性返回。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当没有任何元素的时候，元组退化成 `()`，就叫做 `unit` 类型，是 Rust 中一个非常重要的基础类型和值，`unit` 类型唯一的值实例就是 `()`，与其类型本身的表示相同。\n",
    "\n",
    "比如一个函数没有返回值的时候，它实际默认返回的是这个 `unit` 值。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 结构体\n",
    "\n",
    "结构体也是一种复合类型，它由若干字段组成，每个字段的类型可以不一样。\n",
    "\n",
    "Rust 中使用 `struct` 关键字来定义结构体。比如下面的代码就定义了一个 `User` 类型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "#[derive(Debug)]\n",
    "struct User {\n",
    "    active: bool,\n",
    "    username: String,\n",
    "    email: String,\n",
    "    age: u64,\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面这段代码演示了结构体类型的实例化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "User { active: true, username: \"someusername123\", email: \"someone@example.com\", age: 1 }"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let user1 = User {\n",
    "    active: true,\n",
    "    username: String::from(\"someusername123\"),\n",
    "    email: String::from(\"someone@example.com\"),\n",
    "    age: 1,\n",
    "};\n",
    "\n",
    "user1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 枚举\n",
    "\n",
    "Rust 中使用 `enum` 关键字定义枚举类型。比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "#[derive(Debug)]\n",
    "enum IpAddrKind {\n",
    "    V4,\n",
    "    V6,\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "枚举类型里面的选项叫做此枚举的变体（variants）。变体是其所属枚举类型的一部分。\n",
    "\n",
    "枚举使用变体进行枚举类型的实例化，比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "V4"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let four = IpAddrKind::V4;\n",
    "\n",
    "four"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "V6"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let six = IpAddrKind::V6;\n",
    "six"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，枚举类型也是一种复合类型。\n",
    "\n",
    "但是与结构体不同，结构体类型是里面的所有字段（所有类型）同时起作用，来产生一个具体的实例，而枚举类型是其中的一个变体起作用，来产生一个具体实例，这点区别可以细细品味。\n",
    "\n",
    "学术上，通常把枚举叫作**和类型（sum type）**，把结构体叫作**积类型（product type）**。\n",
    "\n",
    "枚举类型是 Rust 中最强大的复合类型，在后面的课程中我们会看到，枚举就像一个载体，可以携带任何类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rust 中的控制流\n",
    "\n",
    "下面我们来了解一下 Rust 语言的控制流语句。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 分支语句\n",
    "\n",
    "Rust 中使用 `if else` 来构造分支。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "number is divisible by 3\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let number = 6;\n",
    "// 判断数字number能被4，3，2中的哪一个数字整除\n",
    "if number % 4 == 0 {\n",
    "    println!(\"number is divisible by 4\");\n",
    "} else if number % 3 == 0 {\n",
    "    println!(\"number is divisible by 3\");\n",
    "} else if number % 2 == 0 {\n",
    "    println!(\"number is divisible by 2\");\n",
    "} else {\n",
    "    println!(\"number is not divisible by 4, 3, or 2\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与其他 C 系语言不同，Rust 中 `if` 后面的条件表达式不推荐用`()`包裹起来，因为 Rust 设计者认为那个是不必要的，是多余的语法噪音。\n",
    "\n",
    "还要注意一点，`if else` 支持表达式返回。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "y is 101\n"
     ]
    }
   ],
   "source": [
    "let x = 1;\n",
    "// 在这里，if else 返回了值\n",
    "let y = if x == 0 {\n",
    "    // 代码块结尾最后一句不加分号，表示把值返回回去\n",
    "    100\n",
    "} else {\n",
    "    // 代码块结尾最后一句不加分号，表示把值返回回去\n",
    "    101\n",
    "};\n",
    "\n",
    "println!(\"y is {}\", y);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "像上面这样的代码，其实已经实现了类似于 C 语言中的三目运算符这样的设计，在 Rust 中，不需要额外提供那样的特殊语法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 循环语句\n",
    "\n",
    "Rust 中有三种循环语句，分别是 `loop`、`while`、`for`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `loop` 用于无条件（无限）循环"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The result is 20\n"
     ]
    }
   ],
   "source": [
    "let mut counter = 0;\n",
    "\n",
    "// 这里，接收从循环体中返回的值，对result进行初始化\n",
    "let result = loop {\n",
    "    counter += 1;\n",
    "    if counter == 10 {\n",
    "        // 使用break跳出循环，同时带一个返回值回去\n",
    "        break counter * 2;\n",
    "    }\n",
    "};\n",
    "\n",
    "println!(\"The result is {result}\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "请仔细品味这个例子，这种返回一个值到外面对一个变量初始化的方式，是 Rust 中的习惯用法，这能让代码更紧凑。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `while` 循环为条件判断循环。当后面的条件为真的时候，执行循环体里面的代码。和前面的 `if` 语句一样，Rust 中的 `while` 后面的条件表达式不推荐用`()`包裹起来。\n",
    "\n",
    "比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3!\n",
      "2!\n",
      "1!\n",
      "LIFTOFF!!!\n"
     ]
    }
   ],
   "source": [
    "let mut number = 3;\n",
    "\n",
    "while number != 0 {\n",
    "    println!(\"{number}!\");\n",
    "    number -= 1;\n",
    "}\n",
    "\n",
    "println!(\"LIFTOFF!!!\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `for` 循环在 Rust 中，基本上只用于迭代器（暂时可以想象成对数组，动态数组等）的遍历。Rust 中没有 C 语言那种 `for` 循环风格的语法支持，因为那被认为是一种不好的设计。\n",
    "\n",
    "你可以看一下下面的代码，就是对一个数组进行遍历。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "the value is: 10\n",
      "the value is: 20\n",
      "the value is: 30\n",
      "the value is: 40\n",
      "the value is: 50\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "let a = [10, 20, 30, 40, 50];\n",
    "\n",
    "for element in a {\n",
    "    println!(\"the value is: {element}\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面代码对动态数组 `Vec` 的遍历阻止了越界的可能性，因此用这种方式访问 `Vec` 比用下标索引的方式访问更加安全。\n",
    "\n",
    "对于循环的场景，Rust 还提供了一个便捷的语法来生成遍历区间： `..`（两个点）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n",
      "1\n",
      "2\n",
      "3\n",
      "4\n",
      "3\n",
      "2\n",
      "1\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "// 左闭右开区间\n",
    "for number in 1..4 {\n",
    "    println!(\"{number}\");\n",
    "}\n",
    "// 左闭右闭区间\n",
    "for number in 1..=4 {\n",
    "    println!(\"{number}\");\n",
    "}\n",
    "// 反向\n",
    "for number in (1..4).rev() {\n",
    "    println!(\"{number}\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们再来试试打印字符。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a\n",
      "b\n",
      "c\n",
      "d\n",
      "e\n",
      "f\n",
      "g\n",
      "h\n",
      "i\n",
      "j\n",
      "k\n",
      "l\n",
      "m\n",
      "n\n",
      "o\n",
      "p\n",
      "q\n",
      "r\n",
      "s\n",
      "t\n",
      "u\n",
      "v\n",
      "w\n",
      "x\n",
      "y\n",
      "z\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "for ch in 'a'..='z' {\n",
    "    println!(\"{ch}\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rust 中的函数和模块\n",
    "\n",
    "最后我们来看 Rust 的函数、闭包和模块，它们用于封装和复用代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 函数\n",
    "\n",
    "函数基本上是所有编程语言的标配，在 Rust 中也不例外，它是一种基本的代码复用方法。\n",
    "\n",
    "在 Rust 中使用 `fn` 关键字来定义一个函数。比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The value of a b is: 5h\n"
     ]
    }
   ],
   "source": [
    "fn print_a_b(a: i32, b: char) {\n",
    "    println!(\"The value of a b is: {a}{b}\");\n",
    "}\n",
    "\n",
    "print_a_b(5, 'h');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数定义时的参数叫作**形式参数（形参）**，函数调用时传入的参数值叫做**实际参数（实参）**。\n",
    "\n",
    "函数的调用要与函数的签名（函数名、参数个数、参数类型、参数顺序、返回类型）一致，也就是实参和形参要匹配。\n",
    "\n",
    "函数对于几乎所有语言都非常重要，实际上各种编程语言在实现时，都是以函数作为基本单元来组织栈上的内存分配和回收的，这个基本的内存单元就是所谓的**栈帧（frame）**，我们在下节课会讲到。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 闭包（Closure）\n",
    "\n",
    "闭包是另一种风格的函数。它使用两个竖线符号 `||` 定义，而不是用 `fn ()` 来定义。你可以看下面的形式对比。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "ename": "Error",
     "evalue": "expected type, found `|`",
     "output_type": "error",
     "traceback": [
      "    let add_one_v3 = |x: /* Type */|             { x + 1 };",
      "                                   ^ expected type",
      "expected type, found `|`"
     ]
    }
   ],
   "source": [
    "// 标准的函数定义\n",
    "fn  add_one_v1   (x: u32) -> u32 { x + 1 }\n",
    "\n",
    "// 闭包的定义，请注意形式对比\n",
    "let add_one_v2 = |x: u32| -> u32 { x + 1 };\n",
    "\n",
    "// 闭包的定义2，省略了类型标注\n",
    "let add_one_v3 = |x|             { x + 1 }; // error[E0282]: type annotations needed\n",
    "\n",
    "// 闭包的定义3，花括号也省略了\n",
    "let add_one_v4 = |x|               x + 1  ;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 76,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "{\n",
    "    fn  add_one_v1   (x: u32) -> u32 { x + 1 }\n",
    "\n",
    "    // 闭包的定义，请注意形式对比\n",
    "    let add_one_v2 = |x: u32| -> u32 { x + 1 };\n",
    "    \n",
    "    // 闭包的定义2，省略了类型标注\n",
    "    let add_one_v3 = |x: u32|             { x + 1 };\n",
    "    \n",
    "    // 闭包的定义3，花括号也省略了\n",
    "    let add_one_v4 = |x: u32|              x + 1  ;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "闭包与函数的一个显著不同就是，闭包可以捕获函数中的局部变量为我所用，而函数不行。\n",
    "\n",
    "比如，下面示例中的闭包 `add_v2` 捕获了 `main` 函数中的局部变量 `a` 来使用，但是函数 `add_v1` 就没有这个能力。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "ename": "Error",
     "evalue": "can't capture dynamic environment in a fn item",
     "output_type": "error",
     "traceback": [
      "        x + a",
      "            ^ error: can't capture dynamic environment in a fn item",
      "can't capture dynamic environment in a fn item",
      "help: use the `|| { ... }` closure form instead"
     ]
    }
   ],
   "source": [
    "{\n",
    "    let a = 10u32; // 局部变量\n",
    "\n",
    "    fn add_v1(x: u32) -> u32 {\n",
    "        x + a\n",
    "    } // 定义一个内部函数\n",
    "\n",
    "    let result1 = add_v1(20); // 调用函数\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "30\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "{\n",
    "    let a = 10u32; // 局部变量\n",
    "\n",
    "    let add_v2 = |x: u32| -> u32 { x + a }; // 定义一个闭包\n",
    "\n",
    "    let result2 = add_v2(20); // 调用闭包\n",
    "    println!(\"{}\", result2);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "闭包之所以能够省略类型参数等信息，主要是其定义在某个函数体内部，从闭包的内容和上下文环境中能够分析出来那些类型信息。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模块\n",
    "\n",
    "我们不可能把所有代码都写在一个文件里面。代码量多了后，分成不同的文件模块书写是非常自然的事情。\n",
    "\n",
    "这个需求需要从编程语言层级去做一定的支持才行，Rust 也提供了相应的方案。\n",
    "\n",
    "分文件和目录组织代码理解起来其实很简单，主要的知识点在于目录的组织结构上。比如下面示例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "backyard\n",
    "├── Cargo.lock\n",
    "├── Cargo.toml\n",
    "└── src\n",
    "    ├── garden              // 子目录\n",
    "    │   └── vegetables.rs\n",
    "    ├── garden.rs           // 与子目录同名的.rs文件，表示这个模块的入口\n",
    "    └── main.rs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "第五行代码到第七行代码组成 `garden` 模块，在 `garden.rs` 中，使用 `mod vegetables;` 导入 `vegetables` 子模块。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mod vegetables;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 `main.rs` 中，用同样的方式导入 `garden` 模块。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mod garden;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "整个代码结构就这样一层一层地组织起来了。另一种文件的组织形式来自 2015 版，也很常见，有很多人喜欢用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "backyard\n",
    "├── Cargo.lock\n",
    "├── Cargo.toml\n",
    "└── src\n",
    "    ├── garden          // 子目录\n",
    "    │   └── mod.rs      // 子目录中有一个固定文件名 mod.rs，表示这个模块的入口\n",
    "    │   └── vegetables.rs\n",
    "    └── main.rs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "同上，由第五行到第七行代码组成 `garden` 模块，在 `main.rs` 中导入它使用。你可以在本地创建文件，来体会两种不同目录组织形式的区别。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试\n",
    "\n",
    "Rust 语言中自带单元测试和集成测试方案。我们来看一个示例，在 `src/lib.rs` 或 `src/main.rs` 下有一段代码。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [],
   "source": [
    "fn foo() -> u32 { 10u32 }\n",
    "\n",
    "#[cfg(test)]            // 这里配置测试模块\n",
    "mod tests {\n",
    "    use crate::foo;\n",
    "     \n",
    "    #[test]             // 具体的单元测试用例\n",
    "    fn it_works() {\n",
    "        let result = foo();           // 调用被测试的函数或功能\n",
    "        assert_eq!(result, 10u32);    // 断言\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在项目目录下运行 `cargo test`，会输出类似如下结果。\n",
    "\n",
    "```shell\n",
    "running 1 test\n",
    "test tests::it_works ... ok\n",
    "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\n",
    "\n",
    "running 0 tests\n",
    "test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\n",
    "```\n",
    "\n",
    "Rust 自带测试方案，让我们能够非常方便地写测试用例，并且统一了社区的测试设计规范。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 配置文件 Cargo.toml\n",
    "\n",
    "这节课开头的时候，我们简单介绍过 `Cargo.toml`，它是 Rust 语言包和依赖管理器 Cargo 的配置文件，由官方定义约定。\n",
    "\n",
    "写 Rust 代码基本都会按照这种约定来使用它，对所在工程进行配置。这里面其实包含的知识点非常多，后面实战的部分，我们会详细解释用到的特性。\n",
    "\n",
    "我们要对包依赖这件事情的复杂度有所了解。不知道你有没有听说过npm 依赖黑洞？指的就是 `Node.js` 的包依赖太多太琐碎了。这怪不得 `Node.js`，其实 Rust 也类似。\n",
    "\n",
    "包依赖这件事儿，本身就很复杂，可以说这是软件工程固有的复杂性。对固有复杂性，不能绕弯过，只能正面刚。\n",
    "\n",
    "幸运的是，Cargo 工具已经帮我们搞定了包依赖相关方方面面的麻烦事（此刻 C++ 社区羡慕不已）。\n",
    "\n",
    "为了应对这种复杂性，Cargo 工具的提供了非常多的特性，配置起来也相对比较复杂。有兴趣的话，你可以详细了解一下各种[配置属性](https://doc.rust-lang.org/cargo/)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 小结\n",
    "\n",
    "这节课我们洋洋洒洒罗列了写一个 Rust 程序所需要用到的基本的语法结构和数据类型，让你对 Rust 语言有了一个初步印象。这些知识点虽多，但并不复杂。因为这节课呈现的绝大部分元素都能在其他语言中找到，所以理解起来应该不算太难。\n",
    "\n",
    "这节课出现了一个比较重要的指标：**是否能在编译期计算出某个数据类型在运行过程中占用的内存空间的大小**。如果能计算出，那我们称之为固定尺寸的数据类型；如果不能计算出，那我们称之为不固定尺寸的数据类型，或动态数据类型。\n",
    "\n",
    "其实这也很好理解，因为 Rust 要尽量在编译期间多做一些事情，帮我们做安全性的检查。而在编译期只有能计算出内存尺寸的数据类型，才能被更详尽地去分析和检查，就是这个原理。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 思考题\n",
    "\n",
    "- Rust 中能否实现类似 JS 中的 number 这种通用的数字类型呢？\n",
    "- Rust 中能否实现 Python 中那种无限大小的数字类型呢？"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Rust",
   "language": "rust",
   "name": "rust"
  },
  "language_info": {
   "codemirror_mode": "rust",
   "file_extension": ".rs",
   "mimetype": "text/rust",
   "name": "rust",
   "pygment_lexer": "rust",
   "version": ""
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
